001 /* 002 * Copyright (c) 2005-2006 Stephen J. McConnell 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.metro.runtime; 020 021 import java.io.File; 022 import java.io.IOException; 023 import java.net.URI; 024 import java.net.URL; 025 026 import net.dpml.component.Controller; 027 import net.dpml.component.ControlException; 028 import net.dpml.component.ControllerContext; 029 import net.dpml.component.ControllerContextListener; 030 import net.dpml.component.ControllerContextEvent; 031 import net.dpml.component.Model; 032 import net.dpml.component.Component; 033 import net.dpml.component.Composition; 034 035 import net.dpml.lang.Part; 036 import net.dpml.lang.Builder; 037 import net.dpml.lang.StandardClassLoader; 038 import net.dpml.lang.Info; 039 import net.dpml.lang.Category; 040 import net.dpml.lang.Classpath; 041 042 import net.dpml.metro.ComponentModel; 043 import net.dpml.metro.data.ComponentDirective; 044 import net.dpml.metro.data.DefaultComposition; 045 import net.dpml.metro.builder.ComponentDecoder; 046 047 import net.dpml.util.Logger; 048 import net.dpml.util.DefaultLogger; 049 import net.dpml.util.EventQueue; 050 import net.dpml.util.Resolver; 051 052 import org.w3c.dom.Element; 053 054 /** 055 * The composition controller is the controller used to establish remotely accessible 056 * component controls. 057 * 058 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 059 * @version 1.0.1 060 */ 061 public class CompositionController implements Controller, Builder 062 { 063 //-------------------------------------------------------------------- 064 // static 065 //-------------------------------------------------------------------- 066 067 /** 068 * Static URI of this controller. 069 */ 070 public static final URI CONTROLLER_URI = createStaticURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.0.1" ); 071 072 private static final String BASEPATH = setupBasePathSpec(); 073 074 static final URI ROOT_URI = createStaticURI( "metro:/" ); 075 076 //-------------------------------------------------------------------- 077 // immutable state 078 //-------------------------------------------------------------------- 079 080 private final Logger m_logger; 081 private final ControllerContext m_context; 082 private final ComponentController m_controller; 083 private final InternalControllerContextListener m_listener; 084 private final String m_partition; 085 private final EventQueue m_events; 086 087 //-------------------------------------------------------------------- 088 // mutable state 089 //-------------------------------------------------------------------- 090 091 private boolean m_disposed = false; 092 093 //-------------------------------------------------------------------- 094 // constructor 095 //-------------------------------------------------------------------- 096 097 /** 098 * Creation of a new controller. 099 * @param context the control context 100 * @exception ControlException if an error occurs during controller creation 101 */ 102 public CompositionController( ControllerContext context ) 103 throws ControlException 104 { 105 super(); 106 107 m_context = context; 108 m_partition = context.getPartition(); 109 Logger root = new DefaultLogger( m_partition ); 110 m_logger = root.getChildLogger( "dpml.metro" ); 111 m_listener = new InternalControllerContextListener(); 112 m_context.addControllerContextListener( m_listener ); 113 m_controller = new ComponentController( m_logger, this ); 114 m_events = new EventQueue( m_logger ); 115 } 116 117 EventQueue getEventQueue() 118 { 119 return m_events; 120 } 121 122 /** 123 * Controller finalization. 124 * @exception Throwable if a finalization error occurs 125 */ 126 protected void finalize() throws Throwable 127 { 128 if( getLogger().isTraceEnabled() ) 129 { 130 getLogger().trace( 131 "finalizing controller [" 132 + getClass().getName() 133 + "#" 134 + System.identityHashCode( this ) 135 + "]" ); 136 } 137 dispose(); 138 } 139 140 //-------------------------------------------------------------------- 141 // Builder 142 //-------------------------------------------------------------------- 143 144 /** 145 * Construct the deployment information from a part definition. 146 * @param logger the logging channel 147 * @param info the part info definition 148 * @param classpath the part classpath definition 149 * @param strategy the DOM element definining the deplyment streategy 150 * @param resolver build-time uri resolver 151 * @return the part definition 152 * @exception IOException if an I/O error occurs 153 */ 154 public Part build( 155 Logger logger, Info info, Classpath classpath, Element strategy, Resolver resolver ) throws IOException 156 { 157 ClassLoader context = Thread.currentThread().getContextClassLoader(); 158 try 159 { 160 ClassLoader anchor = Component.class.getClassLoader(); 161 Thread.currentThread().setContextClassLoader( anchor ); 162 ComponentDecoder decoder = new ComponentDecoder(); 163 ComponentDirective directive = decoder.buildComponent( strategy, resolver ); 164 return new DefaultComposition( logger, info, classpath, this, directive ); 165 } 166 finally 167 { 168 Thread.currentThread().setContextClassLoader( context ); 169 } 170 } 171 172 //-------------------------------------------------------------------- 173 // PartHandler 174 //-------------------------------------------------------------------- 175 176 /** 177 * Build a classloader stack. 178 * @param name the name to assign to the classloader 179 * @param anchor the anchor classloader to server as the classloader chain root 180 * @param classpath the part classpath definition 181 * @return the new classloader 182 * @exception IOException if an IO error occurs during classpath evaluation 183 */ 184 public ClassLoader getClassLoader( String name, ClassLoader anchor, Classpath classpath ) throws IOException 185 { 186 return getClassLoader( name, anchor, classpath, true ); 187 } 188 189 /** 190 * Build a classloader stack. 191 * @param anchor the anchor classloader to server as the classloader chain root 192 * @param classpath the part classpath definition 193 * @return the new classloader 194 * @exception IOException if an IO error occurs during classpath evaluation 195 */ 196 private ClassLoader getClassLoader( String name, ClassLoader anchor, Classpath classpath, boolean expand ) throws IOException 197 { 198 Logger logger = getLogger(); 199 200 if( expand ) 201 { 202 Classpath cp = classpath.getBaseClasspath(); 203 if( null != cp ) 204 { 205 ClassLoader cl = getClassLoader( name + " (super)", anchor, cp ); 206 return getClassLoader( name, cl, classpath, false ); 207 } 208 } 209 210 Class root = ComponentDirective.class; 211 String classname = root.getName(); 212 ClassLoader base = anchor; 213 214 if( null == classpath ) 215 { 216 ClassLoader management = root.getClassLoader(); 217 return new CompositionClassLoader( logger, name, Category.PROTECTED, management, anchor ); 218 } 219 220 try 221 { 222 anchor.loadClass( classname ); 223 } 224 catch( ClassNotFoundException e ) 225 { 226 ClassLoader management = root.getClassLoader(); 227 base = new CompositionClassLoader( logger, name, Category.PROTECTED, management, anchor ); 228 } 229 230 URI[] apis = classpath.getDependencies( Category.PUBLIC ); 231 ClassLoader api = StandardClassLoader.buildClassLoader( logger, name, Category.PUBLIC, base, apis ); 232 URI[] spis = classpath.getDependencies( Category.PROTECTED ); 233 ClassLoader spi = StandardClassLoader.buildClassLoader( logger, name, Category.PROTECTED, api, spis ); 234 URI[] imps = classpath.getDependencies( Category.PRIVATE ); 235 ClassLoader impl = StandardClassLoader.buildClassLoader( logger, name, Category.PRIVATE, spi, imps ); 236 return impl; 237 } 238 239 //-------------------------------------------------------------------- 240 // Controller 241 //-------------------------------------------------------------------- 242 243 /** 244 * Returns the uri of this controller. 245 * @return the controller uri 246 */ 247 public URI getURI() 248 { 249 return CONTROLLER_URI; 250 } 251 252 /** 253 * Create and return a new management context using the supplied directive uri. 254 * 255 * @param uri a uri identifying a deployment directive 256 * @return the management context model 257 * @exception ControlException if an error occurs 258 * @exception IOException if an error occurs reading the identified resource 259 */ 260 public Model createModel( URI uri ) throws ControlException, IOException 261 { 262 if( getLogger().isTraceEnabled() ) 263 { 264 getLogger().trace( 265 "creating new model from URI" 266 + "\n URI: " + uri ); 267 } 268 Part part = Part.load( uri, false ); 269 if( part instanceof Composition ) 270 { 271 Composition composition = (Composition) part; 272 return createModel( composition ); 273 } 274 else 275 { 276 final String error = 277 "Part class [" 278 + part.getClass().getName() 279 + "] not recognized."; 280 throw new ControllerException( error ); 281 } 282 } 283 284 /** 285 * Create and return a new management context using the supplied directive uri. 286 * 287 * @param composition a composition directive 288 * @return the management model 289 * @exception ControlException if an error occurs 290 * @exception IOException if an I/O error occurs 291 */ 292 public Model createModel( Composition composition ) throws ControlException, IOException 293 { 294 if( getLogger().isTraceEnabled() ) 295 { 296 getLogger().trace( 297 "creating new model from part" 298 + "\n URI: " + composition.getInfo().getURI() ); 299 } 300 if( composition instanceof DefaultComposition ) 301 { 302 DefaultComposition data = (DefaultComposition) composition; 303 return m_controller.createComponentModel( data ); 304 } 305 else 306 { 307 final String error = 308 "Composition class [" 309 + composition.getClass().getName() 310 + "] not recognized."; 311 throw new ControllerException( error ); 312 } 313 } 314 315 /** 316 * Create and return a remote reference to a component handler. 317 * @param uri a uri identifying a deployment directive 318 * @return the component handler 319 * @exception Exception if an error occurs 320 */ 321 public Component createComponent( URI uri ) throws Exception 322 { 323 if( getLogger().isTraceEnabled() ) 324 { 325 getLogger().trace( 326 "creating new component from URI" 327 + "\nURI: " + uri ); 328 } 329 Part part = Part.load( uri, false ); 330 if( part instanceof DefaultComposition ) 331 { 332 DefaultComposition composition = (DefaultComposition) part; 333 Model model = m_controller.createComponentModel( composition ); 334 return createComponent( model, true ); 335 } 336 else 337 { 338 final String error = 339 "Part class [" 340 + part.getClass().getName() 341 + "] is not recognized."; 342 throw new ControllerException( error ); 343 } 344 } 345 346 /** 347 * Create and return a remote reference to a component handler. 348 * @param model the component model 349 * @return the component handler 350 * @exception Exception if an error occurs during component creation 351 */ 352 public Component createComponent( Model model ) throws Exception 353 { 354 return createComponent( model, false ); 355 } 356 357 /** 358 * Create and return a remote reference to a component handler. 359 * @param model the component model 360 * @param flag if true the component is responsible for model retraction 361 * @return the component handler 362 * @exception Exception if an error occurs during component creation 363 */ 364 private Component createComponent( Model model, boolean flag ) throws Exception 365 { 366 if( model instanceof ComponentModel ) 367 { 368 ClassLoader anchor = Logger.class.getClassLoader(); 369 ComponentModel componentModel = (ComponentModel) model; 370 return m_controller.createDefaultComponentHandler( anchor, componentModel, flag ); 371 } 372 else 373 { 374 // 375 // TODO delegate to foreign controller 376 // 377 378 final String error = 379 "Construction of a handler for the context model class [" 380 + model.getClass().getName() 381 + "] is not supported."; 382 throw new ControllerException( error ); 383 } 384 } 385 386 /** 387 * Return the controllers runtime context. The runtime context holds infromation 388 * about the working and temporary directories and a uri identifying the execution 389 * domain. 390 * 391 * @return the runtime context 392 */ 393 public ControllerContext getControllerContext() 394 { 395 return m_context; 396 } 397 398 /** 399 * Return the assigned logging channel. 400 * @return the logging channel 401 */ 402 Logger getLogger() 403 { 404 return m_logger; 405 } 406 407 String getPartition() 408 { 409 return m_partition; 410 } 411 412 void dispose() 413 { 414 if( !m_disposed ) 415 { 416 getLogger().debug( "initating shutdown" ); 417 m_context.removeControllerContextListener( m_listener ); 418 m_events.terminateDispatchThread(); 419 m_disposed = true; 420 getLogger().debug( "shutdown complete" ); 421 } 422 } 423 424 private static URI createStaticURI( String path ) 425 { 426 try 427 { 428 return new URI( path ); 429 } 430 catch( Throwable e ) 431 { 432 return null; 433 } 434 } 435 436 private URI getURIFromURL( URL url ) 437 { 438 try 439 { 440 return new URI( url.toExternalForm() ); 441 } 442 catch( Throwable e ) 443 { 444 throw new RuntimeException( e ); 445 } 446 } 447 448 /** 449 * Controller context listener. 450 */ 451 private class InternalControllerContextListener implements ControllerContextListener 452 { 453 /** 454 * Notify the listener that the working directory has changed. 455 * 456 * @param event the change event 457 */ 458 public void workingDirectoryChanged( ControllerContextEvent event ) 459 { 460 } 461 462 /** 463 * Notify the listener that the temporary directory has changed. 464 * 465 * @param event the change event 466 */ 467 public void tempDirectoryChanged( ControllerContextEvent event ) 468 { 469 } 470 471 /** 472 * Notify listeners of the disposal of the controller. 473 * @param event the context event 474 */ 475 public void controllerDisposal( ControllerContextEvent event ) 476 { 477 dispose(); 478 } 479 } 480 481 static String setupBasePathSpec() 482 { 483 try 484 { 485 String path = System.getProperty( "user.dir" ); 486 File file = new File( path ); 487 URI uri = file.toURI(); 488 URL url = file.toURL(); 489 return url.toString(); 490 } 491 catch( Exception e ) 492 { 493 return e.toString(); 494 } 495 } 496 497 private static String getPartSpec( URI uri ) 498 { 499 String path = uri.toASCIIString(); 500 if( path.startsWith( BASEPATH ) ) 501 { 502 return "./" + path.substring( BASEPATH.length() ); 503 } 504 else 505 { 506 return path; 507 } 508 } 509 }